package com.ihome.framework.core.sftp;

import com.baidu.disconf.core.common.utils.MachineInfo;
import com.ihome.framework.core.exception.ApplicationException;
import com.ihome.framework.core.log.Log;
import com.ihome.framework.core.sftp.monitor.DownloadMonitor;
import com.ihome.framework.core.sftp.monitor.UploadMonitor;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.SftpATTRS;
import com.jcraft.jsch.SftpException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Iterator;
import java.util.Vector;

public class SFtpClientService {

	public static final String SLASH = "/";

	private IChannelFactory sftpChannelFactory;

	private static final Logger LOGGER = LoggerFactory.getLogger(SFtpClientService.class);

	public SFtpClientService(IChannelFactory sftpChannelFactory) {
		super();
		this.sftpChannelFactory = sftpChannelFactory;
	}

	public void upload(String localDir, String fileName, String remoteDir) {
		ChannelSftp channel = (ChannelSftp) sftpChannelFactory.openChannelWithNewSession();
		if (createCjsDownLoadDirectory(channel, remoteDir)) {
			return;
		}
		try {
			channel.put(localDir + SLASH + fileName, remoteDir + SLASH + fileName, new UploadMonitor());
		} catch (SftpException e) {
			LOGGER.error("upload occurs error.", e);
		} finally {
			sftpChannelFactory.closeChannelAndSession(channel);
		}
	}

	/**
	 * 通过流上传文件到SFTP
	 * 
	 * @param inputStream
	 * @param fileName
	 * @param remoteDir
	 */
	public void upload(InputStream inputStream, String fileName, String remoteDir) {
		String op = "SFtpClientService.upload";
		ChannelSftp channel = (ChannelSftp) sftpChannelFactory.openChannelWithNewSession();
		if (createCjsDownLoadDirectory(channel, remoteDir)) {
			return;
		}
		try {
			channel.put(inputStream, remoteDir + SLASH + fileName, new UploadMonitor());
		} catch (SftpException e) {
			LOGGER.error(Log.op(op).msg("upload occurs error.").kv("fileName", fileName).kv("remoteDir", remoteDir)
					.toString(), e);
		} finally {
			sftpChannelFactory.closeChannelAndSession(channel);
		}
	}

	private String getFileName(String path) {
		String[] p = path.split(SLASH);
		if (p != null && p.length > 0) {
			return p[p.length - 1];
		}
		return "";
	}

	private boolean createCjsDownLoadDirectory(ChannelSftp channel, String routeDir) {
		try {
			channel.cd(routeDir);
		} catch (SftpException cd) {
			try {
				channel.mkdir(routeDir);
			} catch (SftpException ex) {
				LOGGER.error("the directory " + routeDir + "  create fail.", ex);
				sftpChannelFactory.closeChannelAndSession(channel);
				return true;// 上传目录不可以创建
			}
		}
		return false;
	}

	private boolean createDirectory(ChannelSftp channel, String routeDir) {
		try {
			channel.cd(routeDir);
		} catch (SftpException cd) {
			try {
				channel.mkdir(routeDir);
			} catch (SftpException e) {
				LOGGER.error("the directory " + routeDir + "  create fail.", e);
				sftpChannelFactory.closeChannelAndSession(channel);
				throw new ApplicationException(1, e.getMessage());
			}
		}
		return true;
	}

	public void download(String remoteDir, String fileName, String localDir) {

		ChannelSftp channel = (ChannelSftp) sftpChannelFactory.openChannelWithNewSession();

		if (createCjsDownLoadDirectory(channel, remoteDir)) {
			return;
		}

		// 如果本地目录不存在，就创建
		File local = new File(localDir);
		if (!local.exists()) {
			local.mkdir();
		}

		try {
			// 下载文件到本地
			channel.get(remoteDir + SLASH + fileName, localDir + SLASH + fileName, new DownloadMonitor());
		} catch (SftpException e) {
			LOGGER.error(Log.op("DownloadFileService.cjsDownload()").kv("remoteFile", remoteDir + SLASH + fileName)
					.msg("file down fail").toString(), e);
		} finally {
			sftpChannelFactory.closeChannelAndSession(channel);
		}
	}

	/**
	 * 下载文件到输出流，注意如果使用内存流的话，需要关注文件大小
	 * 
	 * @param remoteDir
	 * @param fileName
	 * @param outputStream
	 */
	public void download(String remoteDir, String fileName, OutputStream outputStream) {
		ChannelSftp channel = (ChannelSftp) sftpChannelFactory.openChannelWithNewSession();
		if (createCjsDownLoadDirectory(channel, remoteDir)) {
			return;
		}

		try {
			channel.get(remoteDir + SLASH + fileName, outputStream, new DownloadMonitor());
		} catch (SftpException e) {
			LOGGER.error(Log.op("DownloadFileService.cjsDownload()").kv("remoteFile", remoteDir + SLASH + fileName)
					.msg("file down fail").toString(), e);
		} finally {
			sftpChannelFactory.closeChannelAndSession(channel);
		}
	}

	private void processUpload(String remoteDir, String localDir, SFtpFilter filter, ChannelSftp channel)
			throws SftpException {
		createDirectory(channel, remoteDir);
		File dirFile = new File(localDir);
		File[] files = dirFile.listFiles();
		if (filter != null) {
			files = filter.filter(channel, files);
		}
		for (File file : files) {
			String fileName = file.getName();
			if (file.isDirectory()) {
				// processUpload(remoteDir, file.getPath(), validator, channel);
			} else {
				channel.put(localDir + SLASH + fileName, remoteDir + SLASH + fileName, new UploadMonitor());
			}
		}
	}

	/**
	 * 批量上传文件
	 *
	 * @param remoteDir
	 *            远端上传目录
	 * @param localDir
	 *            本地保存目录
	 * @param filter
	 *            sftp文件过滤器
	 * @see SFtpFilter
	 */
	public void batchUpLoad(String remoteDir, String localDir, SFtpFilter filter) {
		ChannelSftp channel = (ChannelSftp) sftpChannelFactory.openChannelWithNewSession();
		try {
			processUpload(remoteDir, localDir, filter, channel);
		} catch (SftpException e) {
			LOGGER.error(Log.op("SFtpClientService.batchUpLoad()").kv("remoteFile", remoteDir).msg("file upload fail")
					.toString(), e);
			throw new ApplicationException(1, e.getMessage());
		} finally {
			sftpChannelFactory.closeChannelAndSession(channel);
		}
	}

	private void processDownload(String remoteDir, String localDir, SFtpFilter validator, ChannelSftp channel)
			throws SftpException {
		Vector v = channel.ls(remoteDir);
		if (validator != null) {
			v = validator.filter(channel, v);
		}
		Iterator it = v.iterator();
		while (it.hasNext()) {
			ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) it.next();
			String fileName = entry.getFilename();
			SftpATTRS attrs = entry.getAttrs();
			if (attrs.isDir()) {
				// processDownload(remoteDir, localDir, validator, channel);
			} else {
				File dir = new File(localDir);
				if (!dir.exists()) {
					dir.mkdirs();
				}
				channel.get(remoteDir + SLASH + fileName, localDir + SLASH + fileName, new DownloadMonitor());
			}
		}
	}

	/**
	 * 批量下载文件
	 *
	 * @param remoteDir
	 *            远程下载目录
	 * @param localDir
	 *            本地保存目录
	 * @param filter
	 *            sftp文件过滤器
	 * @see SFtpFilter
	 */
	public void batchDownLoad(String remoteDir, String localDir, SFtpFilter filter) {
		ChannelSftp channel = (ChannelSftp) sftpChannelFactory.openChannelWithNewSession();
		try {
			processDownload(remoteDir, localDir, filter, channel);
		} catch (SftpException e) {
			LOGGER.error(Log.op("SFtpClientService.batchDownLoad()").kv("remoteFile", remoteDir).msg("file down fail")
					.toString(), e);
			throw new ApplicationException(2, e.getMessage());
		} finally {
			sftpChannelFactory.closeChannelAndSession(channel);
		}
	}

	/**
	 * 传入sftp配置的批量上传方法
	 *
	 * @param remoteDir
	 *            远端目录
	 * @param localDir
	 *            本地目录
	 * @param sftpConfig
	 *            sftp配置
	 * @param filter
	 *            sftp文件过滤器
	 * @see SFtpConfig
	 * @see SFtpFilter
	 */
	public void batchUpLoadWithConfig(String remoteDir, String localDir, SFtpConfig sftpConfig, SFtpFilter filter) {
		ChannelSftp channel = (ChannelSftp) sftpChannelFactory.openChannelWithNewSession(sftpConfig);
		try {
			processUpload(remoteDir, localDir, filter, channel);
		} catch (SftpException e) {
			LOGGER.error(Log.op("SFtpClientService.batchUpLoad()").kv("remoteFile", remoteDir).msg("file upload fail")
					.toString(), e);
			throw new ApplicationException(3, e.getMessage());
		} finally {
			sftpChannelFactory.closeChannelAndSession(channel);
		}
	}

	/**
	 * 传入sftp配置的批量下载方法
	 *
	 * @param remoteDir
	 *            远端目录
	 * @param localDir
	 *            本地目录
	 * @param sftpConfig
	 *            sftp配置
	 * @param filter
	 *            sftp文件过滤器
	 * @see SFtpConfig
	 * @see SFtpFilter
	 */
	public void batchDownLoadWithConfig(String remoteDir, String localDir, SFtpConfig sftpConfig, SFtpFilter filter) {
		ChannelSftp channel = (ChannelSftp) sftpChannelFactory.openChannelWithNewSession(sftpConfig);
		try {
			processDownload(remoteDir, localDir, filter, channel);
		} catch (SftpException e) {
			LOGGER.error(Log.op("SFtpClientService.batchDownLoad()").kv("remoteFile", remoteDir).msg("file down fail")
					.toString(), e);
			throw new ApplicationException(4, e.getMessage());
		} finally {
			sftpChannelFactory.closeChannelAndSession(channel);
		}
	}

	public void batchDownLoad(String remoteDir, String localDir) {
		batchDownLoad(remoteDir, localDir, null);
	}

	public void batchUpLoad(String remoteDir, String localDir) {
		batchUpLoad(remoteDir, localDir, null);
	}

	public void batchUpLoadWithConfig(String remoteDir, String localDir, SFtpConfig sftpConfig) {
		batchUpLoadWithConfig(remoteDir, localDir, sftpConfig, null);
	}

	public void batchDownLoadWithConfig(String remoteDir, String localDir, SFtpConfig sftpConfig) {
		batchDownLoadWithConfig(remoteDir, localDir, sftpConfig, null);
	}

	public void renameWithConfig(String remoteOldPath, String remoteNewPath, SFtpConfig sftpConfig) {
		ChannelSftp channel = (ChannelSftp) sftpChannelFactory.openChannelWithNewSession(sftpConfig);
		try {
			channel.rename(remoteOldPath, remoteNewPath);
		} catch (SftpException e) {
			LOGGER.error(Log.op("SFtpClientService.renameWithConfig()").kv("remoteOldPath", remoteOldPath)
					.kv("remoteNewPath", remoteNewPath).msg("file rename fail").toString(), e);
			throw new ApplicationException(4, e.getMessage());
		} finally {
			sftpChannelFactory.closeChannelAndSession(channel);
		}
	}

	public boolean ifExistFile(String remoteDir, SFtpConfig sftpConfig, SFtpFileValidator filter) {
		ChannelSftp channel = (ChannelSftp) sftpChannelFactory.openChannelWithNewSession(sftpConfig);
		Vector v = null;
		try {
			v = channel.ls(remoteDir);
		} catch (SftpException e) {
			LOGGER.error(Log.op("SFtpClientService.ifExistFile()").kv("remoteFile", remoteDir).msg("dir not exist")
					.toString());
			return false;
		}
		return filter != null && filter.validate(channel, v);
	}
}